min_caml_print_int をアセンブリで実装
libmincaml の min_caml_print_int の実装メモ。
レジスタの用途
table:レジスタ
レジスタ名 別名 説明 備考 要退避
x0 zero ゼロレジスタ x
x1 ra 関数戻りアドレス o
x2 sp スタックポインタ x
x3 gp グローバルポインタ x
x4 tp スレッドポインタ x
x5 t0 作業レジスタ MinCamlで作業レジスタとして利用する x
x6-x7 t1-t2 作業レジスタ x
x8 s0/fp フレームポインタ MinCamlでは使わないけど、退避しておく? o
x9 s1 保存レジスタ △
x10-x11 a0-a1 引数、戻り値 x
x12-x17 a2-a7 引数 x
x18-x25 s2-s9 保存レジスタ △
x26 s10 保存レジスタ (※)MinCamlのスタックポインタとして利用 x
x27 s11 保存レジスタ (※)MinCamlのヒープポインタとして利用 x
x28-x31 t3-t6 作業レジスタ x
退避が必要なレジスタ
以下のレジスタは退避が必要
常に退避
ra レジスタ
fp レジスタ(MinCamlでは使わないが、Cと連携することがあるかもしれないので、念のため退避しておく)
使う時は退避しておく
s1 ~ s9 レジスタ
int の範囲
0x7fffffff ~ 0x80000000
2147483647 ~ -2147483648
最大10ケタ(1_000_000_000)
アルゴリズム
s0 = a0
if 先頭1ビットが0の場合
何もしない
先頭1ビットが1の場合
putc '-'
s0 = (0xfffffffd ^ 0xffffffff) + 1
【10桁目】「10億」で割って商を求める
0の場合→何もしない
0以外の場合→ putc ('0'(48) + 商)
【9桁目】「1億」で割って商を求める
0の場合→何もしない
0以外の場合→ putc ('0'(48) + 商)
「10億→1億→1000万→100万→10万→1万→1000→100→10→1」で商を求めプリントする
ひとまずRubyで実装してみる。
code:print_int_test.rb
def print_int(n)
s0 = n
# 最上位ビットを取得
msb = (n & 0x80000000) >> 31
if msb == 1
# "-" を出力
putc "-"
# 負数の絶対値を取得
s0 = ((s0 & 0xffffffff) ^ 0xffffffff) + 1
end
# 10ケタ目を出力
q = div(s0, 1000000000)
s0 = s0 - q * 1000000000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 9ケタ目を出力
q = div(s0, 100000000)
s0 = s0 - q * 100000000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 8ケタ目を出力
q = div(s0, 10000000)
s0 = s0 - q * 10000000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 7ケタ目を出力
q = div(s0, 1000000)
s0 = s0 - q * 1000000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 6ケタ目を出力
q = div(s0, 100000)
s0 = s0 - q * 100000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 5ケタ目を出力
q = div(s0, 10000)
s0 = s0 - q * 10000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 4ケタ目を出力
q = div(s0, 1000)
s0 = s0 - q * 1000
c = ('0'.ord + q).chr
putc(c) if q > 0
# 3ケタ目を出力
q = div(s0, 100)
s0 = s0 - q * 100
c = ('0'.ord + q).chr
putc(c) if q > 0
# 2ケタ目を出力
q = div(s0, 10)
s0 = s0 - q * 10
c = ('0'.ord + q).chr
putc(c) if q > 0
# 1ケタ目を出力
putc ('0'.ord + s0).chr
end
# 割り算
def div(n, d)
raise "err" if n < 0
raise "err" if d <= 0
q = 0
r = n
while r >= d
r = r - d
q = q + 1
end
return q
end
def print_newline
print "\n"
end
print_int(0); print_newline
print_int(1); print_newline
print_int(9); print_newline
print_int(10); print_newline
print_int(11); print_newline
print_int(100); print_newline
print_int(1111); print_newline
print_int(22222); print_newline
print_int(2147483647); print_newline
print_int(-1); print_newline
print_int(-2); print_newline
print_int(-2147483648); print_newline
上記Rubyのコードをアセンブリ言語に移植。
code:libmincaml.S
//---------------------------------------------------------
// print_int: 整数を出力
// a0: 出力する整数
//---------------------------------------------------------
.text
.globl min_caml_print_int
min_caml_print_int:
// 使用するレジスタ
// s1: 出力する整数(作業用)
// s2: 商
// レジスタをスタックへ退避
addi s10, s10, 12
sw ra, -12(s10)
sw s1, -8(s10)
sw s2, -4(s10)
bgt a0, zero, min_caml_print_int_positive
min_caml_print_int_negative:
// 負数の場合、s1 に絶対値を格納
mv s1, a0
li t1, 0xffffffff
xor s1, s1, t1
// 出力する整数を、作業用レジスタ s1 にコピー
addi s1, s1, 1
// "-" を出力
li a0, '-'
call putc
j min_caml_print_int_main
min_caml_print_int_positive:
// 出力する整数を、作業用レジスタ s1 にコピー
mv s1, a0
min_caml_print_int_main:
// 10ケタ目を出力
mv a0, s1 // 割られる数
li a1, 1000000000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 9ケタ目を出力
mv a0, s1 // 割られる数
li a1, 100000000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 8ケタ目を出力
mv a0, s1 // 割られる数
li a1, 10000000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 7ケタ目を出力
mv a0, s1 // 割られる数
li a1, 1000000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 6ケタ目を出力
mv a0, s1 // 割られる数
li a1, 100000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 5ケタ目を出力
mv a0, s1 // 割られる数
li a1, 10000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 4ケタ目を出力
mv a0, s1 // 割られる数
li a1, 1000 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 3ケタ目を出力
mv a0, s1 // 割られる数
li a1, 100 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 2ケタ目を出力
mv a0, s1 // 割られる数
li a1, 10 // 割る数
call div
mv s1, a1 // 新しい割られる数
call print_num
// 1ケタ目を出力
li t1, '0'
add t2, t1, s1 // 出力する文字
mv a0, t2
call putc
// レジスタの値を復元
lw ra, -12(s10)
lw s1, -8(s10)
lw s2, -4(s10)
addi s10, s10, -12
ret
// 1桁の数字を出力。ただし、0の場合は何も出力しない
// a0: 出力する数字
print_num:
// レジスタをスタックへ退避
addi s10, s10, 4
sw ra, -4(s10)
// メイン処理
beq a0, zero, print_num_break
li t1, '0'
add t2, t1, a0 // 出力する文字
mv a0, t2
call putc
print_num_break:
// レジスタの値を復元
lw ra, -4(s10)
addi s10, s10, -4
ret
// 割り算
// a0: 割られる数
// a1: 割る数
// 戻り値: 商
div:
// レジスタをスタックへ退避
addi s10, s10, 4
sw ra, -4(s10)
// メイン処理
li t1, 0 // 商
mv t2, a0 // 割られる数
div_loop:
// 「割られる数 < 割る数」になるまで以下を繰り返す
blt t2, a1, div_break
// 割られる数から割る数を引く
sub t2, t2, a1
// 商をインクリメント
addi t1, t1, 1
// ループ
j div_loop
div_break:
// 商を返す
mv a0, t1
// 残った割られる数を返す
mv a1, t2
// レジスタの値を復元
lw ra, -4(s10)
addi s10, s10, -4
ret
//---------------------------------------------------------
// getc: UARTから受信データを読み込む
// 戻り値: 受信データ
//---------------------------------------------------------
.globl getc
getc:
// ra をスタックへ退避
addi s10, s10, 4
sw ra, -4(s10)
// 受信データが来るまで待機
lw t0, UART_CTRL_OFFSET(gp)
li t1, 1
beq t0, t1, getc_break
j getc
getc_break:
lw a0, UART_DATA_OFFSET(gp)
// ra レジスタの値を復元
lw ra, -4(s10)
addi s10, s10, -4
ret
//---------------------------------------------------------
// putc: UARTへ送信データを書き込む
// a0: 送信データ
//---------------------------------------------------------
.globl putc
putc:
// ra をスタックへ退避
addi s10, s10, 4
sw ra, -4(s10)
// メイン処理
sw a0, UART_DATA_OFFSET(gp)
putc_wait:
// 送信が終わるまで待機
lw t0, UART_CTRL_OFFSET(gp)
li t2, 2 // 送信中
li t3, 3 // 送信中かつ受信済
beq t0, t2, putc_wait
beq t0, t3, putc_wait
putc_break:
// ra レジスタの値を復元
lw ra, -4(s10)
addi s10, s10, -4
ret
//---------------------------------------------------------
// 改行コードを出力
//---------------------------------------------------------
.globl put_newline
put_newline:
// ra をスタックへ退避
addi s10, s10, 4
sw ra, -4(s10)
// 改行コードを出力
li a0, '\n'
call putc
li a0, '\r'
call putc
// ra レジスタの値を復元
lw ra, -4(s10)
addi s10, s10, -4
ret
//---------------------------------------------------------
// 1秒間スリープ
//---------------------------------------------------------
.globl sleep
sleep:
// ra をスタックへ退避
addi s10, s10, 4
sw ra, -4(s10)
// メイン処理
li t0, 0
sleep_loop:
addi t0, t0, 1
li t1, 1000000
beq t0, t1, sleep_break
j sleep_loop
sleep_break:
// ra レジスタの値を復元
lw ra, -4(s10)
addi s10, s10, -4
ret